package util

import (
	"bytes"
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	uuid "github.com/satori/go.uuid"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
	"gopkg.in/mgo.v2/bson"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

func PostJson(ctx context.Context, url string, body interface{}, result interface{}, timeout ...time.Duration) error {
	req, err := json.Marshal(body)
	if err != nil {
		return err
	}
	return doRequest(ctx, "POST", url, bytes.NewBuffer(req), result, nil, timeout...)
}

func Get(url string, result interface{}, timeout ...time.Duration) error {
	return doRequest(nil, "GET", url, nil, result, nil, timeout...)
}

func GetJson(ctx context.Context, url string, result interface{}, timeout ...time.Duration) error {
	return doRequest(ctx, "GET", url, nil, result, nil, timeout...)
}

func doRequest(ctx context.Context, method, url string, body io.Reader, result interface{}, header map[string]string, timeout ...time.Duration) error {
	client := http.DefaultClient

	if len(timeout) > 0 {
		client = &http.Client{Timeout: timeout[0], Transport: client.Transport}
	}

	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return err
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	if header != nil {
		for k, s := range header {
			req.Header.Set(k, s)
		}
	}

	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	defer resp.Body.Close()

	// 500 解析错误 message
	if resp.StatusCode == http.StatusInternalServerError {
		res, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		var respData struct {
			Message string `json:"message"`
		}
		err = json.Unmarshal(res, &respData)
		if err != nil {
			return err
		}
		if respData.Message != "" {
			return errors.New(respData.Message)
		}
		return fmt.Errorf("err, StatusCode = %d", resp.StatusCode)
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("err, StatusCode = %d", resp.StatusCode)
	}

	if result != nil {
		body, err := ioutil.ReadAll(resp.Body)
		//fmt.Println(string(body))
		if err != nil {
			return err
		}
		err = json.Unmarshal(body, &result)
		if err != nil {
			return err
		}
	}

	return nil
}

// 获取当前路径
func GetAppPath() string {
	var _, b, _, _ = runtime.Caller(0)
	return filepath.Join(filepath.Dir(b), "../")
}

//解码json文件为map
func jsonFileToMap(fileUrl string) map[string]interface{} {
	//打开json文件
	srcFile, _ := os.Open(fileUrl)
	defer srcFile.Close()
	//创建接收数据的map类型数据
	datamap := make(map[string]interface{})
	//创建解码器
	decoder := json.NewDecoder(srcFile)
	//解码
	err := decoder.Decode(&datamap)
	if err != nil {
		fmt.Println("解码失败,err=", err)
		return datamap
	}
	fmt.Println("解码成功", datamap)
	return datamap
}

//将json数据转为map
func jsonToMap(jsonStr string) map[string]interface{} {
	//将json数据转为字节数据
	jsonbytes := []byte(jsonStr)
	dataMap := make(map[string]interface{})

	err := json.Unmarshal(jsonbytes, &dataMap)
	if err != nil {
		fmt.Println("反序列化失败,err=", err)
	}
	fmt.Println(dataMap)
	return dataMap
}

//将json数据转换为map切片
func jsonToMapSlice(jsonStr string) []map[string]interface{} {
	jsonBytes := []byte(jsonStr)
	dataSlice := make([]map[string]interface{}, 0)
	err := json.Unmarshal(jsonBytes, &dataSlice)
	if err != nil {
		fmt.Println("反序列化失败,err=", err)
	}
	fmt.Println(dataSlice)
	return dataSlice
}

func pkcs5UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

func AesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = errors.New("bad encrypted msg or key")
		}
	}()

	block, _ := aes.NewCipher(key)
	blockSize := block.BlockSize()
	blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
	decrypted = make([]byte, len(encrypted))
	blockMode.CryptBlocks(decrypted, encrypted)
	decrypted = pkcs5UnPadding(decrypted)
	return decrypted, nil
}

func AesEncryptCBC(origData []byte, key []byte) (encrypted []byte, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = errors.New("bad key")
		}
	}()

	block, _ := aes.NewCipher(key)
	blockSize := block.BlockSize()
	origData = pkcs5Padding(origData, blockSize)
	blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
	encrypted = make([]byte, len(origData))
	blockMode.CryptBlocks(encrypted, origData)
	return encrypted, nil
}

func GetClientIP(c *gin.Context) string {
	ClientIP := c.ClientIP()
	fmt.Println("ClientIP:", ClientIP)
	RemoteIP, _ := c.RemoteIP()
	fmt.Println("RemoteIP:", RemoteIP)
	ip := c.Request.Header.Get("X-Forwarded-For")
	if strings.Contains(ip, "127.0.0.1") || ip == "" {
		ip = c.Request.Header.Get("X-real-ip")
	}
	if ip == "" {
		ip = "127.0.0.1"
	}
	if RemoteIP.String() != "127.0.0.1" {
		ip = RemoteIP.String()
	}
	if ClientIP != "127.0.0.1" {
		ip = ClientIP
	}
	return ip
}

//func Ping(target string) bool {
//	pinger, err := ping.NewPinger(target)
//	if err != nil {
//		panic(err)
//	}
//
//	pinger.Count = 1
//	pinger.Timeout = 1 * time.Second
//	pinger.Run() // blocks until finished
//
//	stats := pinger.Statistics()
//
//	// 有回包，就是说明IP是可用的
//	if stats.PacketsRecv >= 1 {
//		return true
//	}
//	return false
//}

func IsPathExist(path string) bool {
	_, err := os.Stat(path)
	return err == nil || os.IsExist(err)
}

func DownloadFile(dirName, url, newName string) (fileName string, err error) {
	if !IsPathExist(dirName) {
		err = os.MkdirAll(dirName, 0755)
		if err != nil {
			//log.Errorf("os.MkDirAll: %s, err: %s\n", dirName, err)
			return "", err
		}
	}
	var tryTimes = 5
	for i := 0; i < tryTimes; i++ {
		fileName, err = downloadFile(dirName, url, newName)
		if err == nil {
			return fileName, nil
		}
	}
	return "", err
}

func downloadFile(dirName, url, newName string) (string, error) {
	if !strings.HasPrefix(url, "http") {
		url = "http://" + url
	}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		//log.Errorf("download file(%s) failed: %v", url, err)
		return "", err
	}

	client := http.Client{}
	response, err := client.Do(req)
	if err != nil {
		//log.Errorf("download file(%s) failed: %v", url, err)
		return "", err
	}

	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		err = fmt.Errorf("response status is not ok: %s", response.Status)
		//log.Errorf("download file(%s) failed: %v", url, err)
		return "", err
	}

	name := filepath.Join(dirName, newName)
	out, err := os.Create(name)
	defer out.Close()

	if err != nil {
		//log.Errorf("create file(%s) failed: %v", name, err)
		return "", err
	}
	_, err = io.Copy(out, response.Body)
	return name, err
}

// JsonStr
func JsonStr(obj interface{}) string {
	raw, err := json.Marshal(obj)
	if err != nil {
		return ""
	}
	return string(raw)
}

// ConvByJson
func ConvByJson(src interface{}, dest interface{}) error {
	tmpbs, err := json.Marshal(src)
	if err != nil {
		return err
	}
	return json.Unmarshal(tmpbs, dest)
}

// ConvByBson
func ConvByBson(src interface{}, dest interface{}) error {
	tmpbs, err := bson.Marshal(src)
	if err != nil {
		return err
	}
	return bson.Unmarshal(tmpbs, dest)
}

// GetLocalIP
func GetLocalIP() string {
	addrArr, err := net.InterfaceAddrs()
	if nil != err {
		return ""
	}
	for _, addr := range addrArr {
		ipnet, ok := addr.(*net.IPNet)
		if ok && !ipnet.IP.IsLoopback() &&
			ipnet.IP.To4() != nil {
			return ipnet.IP.String()
		}
	}
	return ""
}

func Md5(data []byte) string {
	h := md5.New()
	h.Write(data)
	return hex.EncodeToString(h.Sum(nil))
}

func ReadFile(path string) (buffer []byte, err error) {
	file, err := os.Open(path)
	if err != nil {
		Log().Error("os.Open(%s): %+v", path, err)
		return
	}
	defer file.Close()
	buffer, err = ioutil.ReadAll(file)
	if err != nil {
		Log().Error("ioutil.ReadAll(%s): %+v", path, err)
		return
	}
	return buffer, nil
}

func GenUuid() string {
	return uuid.NewV4().String()
}

func Utf8ToGbk(s []byte) ([]byte, error) {
	reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder())
	d, e := ioutil.ReadAll(reader)
	if e != nil {
		return nil, e
	}
	return d, nil
}

func GbkToUtf8(s []byte) ([]byte, error) {
	reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())
	d, e := ioutil.ReadAll(reader)
	if e != nil {
		return nil, e
	}
	return d, nil
}

//convert UTF-8 to GBK
func EncodeGBK(s string) (string, error) {
	I := strings.NewReader(s)
	O := transform.NewReader(I, simplifiedchinese.GBK.NewEncoder())
	d, e := ioutil.ReadAll(O)
	if e != nil {
		return "", e
	}
	return string(d), nil
}

//convert GBK to UTF-8
func DecodeGBK(s string) (string, error) {
	I := strings.NewReader(s)
	O := transform.NewReader(I, simplifiedchinese.GBK.NewDecoder())
	d, e := ioutil.ReadAll(O)
	if e != nil {
		return "", e
	}
	return string(d), nil
}

func Utf8ToHZGB2312(s []byte) ([]byte, error) {
	reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.HZGB2312.NewEncoder())
	d, e := ioutil.ReadAll(reader)
	if e != nil {
		return nil, e
	}
	return d, nil
}

func CopyFile(from, to string) error {
	fromInfo, err := os.Stat(from)
	if err != nil {
		return err
	}

	switch fromInfo.Mode() & os.ModeType {
	case os.ModeSymlink:
		link, err := os.Readlink(from)
		if err != nil {
			return err
		}
		return os.Symlink(link, to)
	default:
		if !fromInfo.Mode().IsRegular() {
			return errors.New(from + " is not a regular file")
		}

		fromFile, err := os.Open(from)
		if err != nil {
			return err
		}
		defer fromFile.Close()

		toFile, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE, 0644)
		if err != nil {
			return err
		}
		defer toFile.Close()
		_, err = io.Copy(toFile, fromFile)
		return err
	}
}

func CopyRecursively(from, to string) error {
	fromInfo, err := os.Stat(from)
	if err != nil {
		return err
	}
	if !fromInfo.IsDir() {
		return CopyFile(from, to)
	}

	infos, err := ioutil.ReadDir(from)
	if err != nil {
		return err
	}

	for _, info := range infos {
		srcPath := filepath.Join(from, info.Name())
		destPath := filepath.Join(to, info.Name())
		if info.IsDir() {
			err = os.MkdirAll(destPath, 0755)
			if err != nil && !os.IsExist(err) {
				return err
			}
			err = CopyRecursively(srcPath, destPath)
			if err != nil {
				return err
			}
		} else {
			err = CopyFile(srcPath, destPath)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func GetAllFile(pathName string, s []string) ([]string, error) {
	rd, err := ioutil.ReadDir(pathName)
	if err != nil {
		fmt.Println("read dir fail:", err)
		return s, err
	}
	for _, fi := range rd {
		if !fi.IsDir() {
			fullName := pathName + "/" + fi.Name()
			s = append(s, fullName)
		}
	}
	return s, nil
}

func FileNameWithOutExt(fileName string) string {
	ext := path.Ext(fileName)
	fileNameOnly := strings.TrimSuffix(fileName, ext)
	return fileNameOnly
}

type ByFileName []os.FileInfo

func (fis ByFileName) Len() int {
	return len(fis)
}

func (fis ByFileName) Swap(i, j int) {
	fis[i], fis[j] = fis[j], fis[i]
}

func (fis ByFileName) Less(i, j int) bool {
	s1 := FileNameWithOutExt(fis[i].Name())
	s2 := FileNameWithOutExt(fis[j].Name())
	return strings.Compare(s1, s2) < 0
}
